﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Text;

/// <summary>
///CaptchaImage 的摘要说明
/// </summary>
public class CaptchaImage
{

    private BackgroundNoiseLevel _backgroundNoise = BackgroundNoiseLevel.None;
    private Color _captBackColor = Color.White;
    private Color _captFontColor = Color.Black;
    private string _fontFamilyName = "";
    private FontWarpFactor _fontWarp = FontWarpFactor.None;
    private string _fontWhitelist = "arial;arial black;comic sans ms;courier new;estrangelo edessa;franklin gothic medium;georgia;lucida console;lucida sans unicode;mangal;microsoft sans serif;palatino linotype;sylfaen;tahoma;times new roman;trebuchet ms;verdana";
    private DateTime _generatedAt = DateTime.Now;
    private int _height = 50;
    private LineNoiseLevel _lineNoise = LineNoiseLevel.None;
    private Random _rand = new Random();
    private string _text = "ABCDE";
    private int _width = 180;
    private static string[] static_RandomFontFamily_ff;


    private void AddLine(Graphics graphics1, Rectangle rect)
    {
        int length = 0;
        float width = 0f;
        int linecount = 0;
        switch (this._lineNoise)
        {
            case LineNoiseLevel.None:
                return;

            case LineNoiseLevel.Low:
                length = 4;
                width = Convert.ToSingle((double)(((double)this._height) / 31.25));
                linecount = 1;
                break;

            case LineNoiseLevel.Medium:
                length = 5;
                width = Convert.ToSingle((double)(((double)this._height) / 27.7777));
                linecount = 1;
                break;

            case LineNoiseLevel.High:
                length = 3;
                width = Convert.ToSingle((int)(this._height / 0x19));
                linecount = 2;
                break;

            case LineNoiseLevel.Extreme:
                length = 3;
                width = Convert.ToSingle((double)(((double)this._height) / 22.7272));
                linecount = 3;
                break;
        }
        PointF[] pf = new PointF[length + 1];
        Pen p = new Pen(Color.Black, width);
        for (int l = 1; l <= linecount; l++)
        {
            for (int i = 0; i <= length; i++)
            {
                pf[i] = this.RandomPoint(rect);
            }
            graphics1.DrawCurve(p, pf, 1.75f);
        }
        p.Dispose();
    }

    private void AddNoise(Graphics graphics1, Rectangle rect)
    {
        int density = 0;
        int size = 0;
        switch (this._backgroundNoise)
        {
            case BackgroundNoiseLevel.None:
                return;

            case BackgroundNoiseLevel.Low:
                density = 30;
                size = 40;
                break;

            case BackgroundNoiseLevel.Medium:
                density = 0x12;
                size = 40;
                break;

            case BackgroundNoiseLevel.High:
                density = 0x10;
                size = 0x27;
                break;

            case BackgroundNoiseLevel.Extreme:
                density = 12;
                size = 0x26;
                break;
        }
        SolidBrush br = new SolidBrush(this._captFontColor);
        int max = Convert.ToInt32((int)(Math.Max(rect.Width, rect.Height) / size));
        for (int i = 0; i <= Convert.ToInt32((int)((rect.Width * rect.Height) / density)); i++)
        {
            graphics1.FillEllipse(br, this._rand.Next(rect.Width), this._rand.Next(rect.Height), this._rand.Next(max), this._rand.Next(max));
        }
        br.Dispose();
    }

    private Bitmap GenerateImagePrivate()
    {
        System.Drawing.Font fnt = null;
        Bitmap bmp = new Bitmap(this._width, this._height, PixelFormat.Format32bppArgb);
        Graphics gr = Graphics.FromImage(bmp);
        gr.SmoothingMode = SmoothingMode.AntiAlias;
        Rectangle rect = new Rectangle(0, 0, this._width, this._height);
        Brush br = new SolidBrush(this._captBackColor);
        gr.FillRectangle(br, rect);
        int charOffset = 0;
        double charWidth = this._width / this._text.Length;
        foreach (char c in this._text)
        {
            fnt = this.GetFont();
            Rectangle rectChar = new Rectangle(Convert.ToInt32((double)(charOffset * charWidth)), 0, Convert.ToInt32(charWidth), this._height);
            GraphicsPath gp = this.TextPath(c.ToString(), fnt, rectChar);
            this.WarpText(gp, rectChar);
            br = new SolidBrush(this._captFontColor);
            gr.FillPath(br, gp);
            charOffset++;
        }
        this.AddNoise(gr, rect);
        this.AddLine(gr, rect);
        fnt.Dispose();
        br.Dispose();
        gr.Dispose();
        return bmp;
    }

    public static string GenerateRandomText(string templateChars, int length)
    {
        StringBuilder sb = new StringBuilder(length);
        int maxLength = templateChars.Length;
        Random rand = new Random();
        for (int i = 0; i <= (length - 1); i++)
        {
            sb.Append(templateChars.Substring(rand.Next(templateChars.Length), 1));
        }
        return sb.ToString();
    }

    private System.Drawing.Font GetFont()
    {
        float fsize = 0f;
        string fname = this._fontFamilyName;
        if (fname == "")
        {
            fname = this.RandomFontFamily();
        }
        switch (this.FontWarp)
        {
            case FontWarpFactor.None:
                fsize = Convert.ToInt32((double)(this._height * 0.7));
                break;

            case FontWarpFactor.Low:
                fsize = Convert.ToInt32((double)(this._height * 0.8));
                break;

            case FontWarpFactor.Medium:
                fsize = Convert.ToInt32((double)(this._height * 0.85));
                break;

            case FontWarpFactor.High:
                fsize = Convert.ToInt32((double)(this._height * 0.9));
                break;

            case FontWarpFactor.Extreme:
                fsize = Convert.ToInt32((double)(this._height * 0.95));
                break;
        }
        return new System.Drawing.Font(fname, fsize, FontStyle.Bold);
    }

    private string RandomFontFamily()
    {
        if (static_RandomFontFamily_ff == null)
        {
            static_RandomFontFamily_ff = this._fontWhitelist.Split(new char[] { ';' });
        }
        return static_RandomFontFamily_ff[this._rand.Next(0, static_RandomFontFamily_ff.Length)];
    }

    private PointF RandomPoint(Rectangle rect)
    {
        return this.RandomPoint(rect.Left, rect.Width, rect.Top, rect.Height);
    }

    private PointF RandomPoint(int xmin, int xmax, int ymin, int ymax)
    {
        return new PointF((float)this._rand.Next(xmin, xmax), (float)this._rand.Next(ymin, ymax));
    }

    public Bitmap RenderImage()
    {
        return this.GenerateImagePrivate();
    }

    private GraphicsPath TextPath(string s, System.Drawing.Font f, Rectangle r)
    {
        StringFormat sf = new StringFormat
        {
            Alignment = StringAlignment.Near,
            LineAlignment = StringAlignment.Near
        };
        GraphicsPath gp = new GraphicsPath();
        gp.AddString(s, f.FontFamily, (int)f.Style, f.Size, r, sf);
        return gp;
    }

    private void WarpText(GraphicsPath textPath, Rectangle rect)
    {
        float WarpDivisor = 0f;
        float RangeModifier = 0f;
        switch (this._fontWarp)
        {
            case FontWarpFactor.None:
                return;

            case FontWarpFactor.Low:
                WarpDivisor = 6f;
                RangeModifier = 1f;
                break;

            case FontWarpFactor.Medium:
                WarpDivisor = 5f;
                RangeModifier = 1.3f;
                break;

            case FontWarpFactor.High:
                WarpDivisor = 4.5f;
                RangeModifier = 1.4f;
                break;

            case FontWarpFactor.Extreme:
                WarpDivisor = 4f;
                RangeModifier = 1.5f;
                break;
        }
        RectangleF rectF = new RectangleF(Convert.ToSingle(rect.Left), 0f, Convert.ToSingle(rect.Width), (float)rect.Height);
        int hrange = Convert.ToInt32((float)(((float)rect.Height) / WarpDivisor));
        int wrange = Convert.ToInt32((float)(((float)rect.Width) / WarpDivisor));
        int left = rect.Left - Convert.ToInt32((float)(wrange * RangeModifier));
        int top = rect.Top - Convert.ToInt32((float)(hrange * RangeModifier));
        int width = (rect.Left + rect.Width) + Convert.ToInt32((float)(wrange * RangeModifier));
        int height = (rect.Top + rect.Height) + Convert.ToInt32((float)(hrange * RangeModifier));
        if (left < 0)
        {
            left = 0;
        }
        if (top < 0)
        {
            top = 0;
        }
        if (width > this.Width)
        {
            width = this.Width;
        }
        if (height > this.Height)
        {
            height = this.Height;
        }
        PointF leftTop = this.RandomPoint(new Rectangle(left, top, left + wrange, top + hrange));
        PointF rightTop = this.RandomPoint(new Rectangle(width - wrange, top, width, top + hrange));
        PointF leftBottom = this.RandomPoint(new Rectangle(left, height - hrange, left + wrange, height));
        PointF rightBottom = this.RandomPoint(new Rectangle(width - wrange, height - hrange, width, height));
        PointF[] points = new PointF[] { leftTop, rightTop, leftBottom, rightBottom };
        Matrix m = new Matrix();
        m.Translate(0f, 0f);
        textPath.Warp(points, rectF, m, WarpMode.Perspective, 0f);
    }


    public BackgroundNoiseLevel BackgroundNoise
    {
        get
        {
            return this._backgroundNoise;
        }
        set
        {
            this._backgroundNoise = value;
        }
    }

    public string CaptBackColor
    {
        get
        {
            return this._captBackColor.Name;
        }
        set
        {
            try
            {
                this._captBackColor = Color.FromName(value);
            }
            catch
            {
                this._captBackColor = Color.White;
            }
        }
    }

    public string CaptFontColor
    {
        get
        {
            return this._captFontColor.Name;
        }
        set
        {
            try
            {
                this._captFontColor = Color.FromName(value);
            }
            catch
            {
                this._captFontColor = Color.Black;
            }
        }
    }

    public string Font
    {
        get
        {
            return this._fontFamilyName;
        }
        set
        {
            try
            {
                System.Drawing.Font font1 = new System.Drawing.Font(value, 12f);
                this._fontFamilyName = value;
                font1.Dispose();
            }
            catch
            {
                this._fontFamilyName = FontFamily.GenericSerif.Name;
            }
        }
    }

    public FontWarpFactor FontWarp
    {
        get
        {
            return this._fontWarp;
        }
        set
        {
            this._fontWarp = value;
        }
    }

    public string FontWhitelist
    {
        get
        {
            return this._fontWhitelist;
        }
        set
        {
            this._fontWhitelist = value;
        }
    }

    public int Height
    {
        get
        {
            return this._height;
        }
        set
        {
            if (value <= 20)
            {
                throw new ArgumentOutOfRangeException("height", value, "height must be greater than 30.");
            }
            this._height = value;
        }
    }

    public LineNoiseLevel LineNoise
    {
        get
        {
            return this._lineNoise;
        }
        set
        {
            this._lineNoise = value;
        }
    }

    public DateTime RenderedAt
    {
        get
        {
            return this._generatedAt;
        }
    }

    public string Text
    {
        get
        {
            return this._text;
        }
        set
        {
            this._text = value;
        }
    }

    public int Width
    {
        get
        {
            return this._width;
        }
        set
        {
            if (value <= 60)
            {
                throw new ArgumentOutOfRangeException("width", value, "width must be greater than 60.");
            }
            this._width = value;
        }
    }

    public enum BackgroundNoiseLevel
    {
        None,
        Low,
        Medium,
        High,
        Extreme
    }

    public enum FontWarpFactor
    {
        None,
        Low,
        Medium,
        High,
        Extreme
    }

    public enum LineNoiseLevel
    {
        None,
        Low,
        Medium,
        High,
        Extreme
    }

}